【连载】聊聊 APK(二)——Dex 热修复与 Classpath
想进大厂,就关注「 程序亦非猿 」
时不时 8:38 推送优质文章,觉得有用,置顶加星标
船长导读:「聊聊 APK」系列由我好基友 Gemini 老师提供
上一篇文章《聊聊 APK —— 直接运行 Dex》我们知道了怎么在手机上运行 Dex,那么和 Dex 文件最相关的应用技术可能就是热修复了。
热修复与 Classloader
参照腾讯开源的 Tinker 和阿里的 DexPatch 的原理,我们知道对于现在对于 java 代码的热修复主要从 DexClassLoader
里面的 dexPathList
入手,这里应用的原理就是 classloader 双亲委派里对于加载后的类的缓存机制。
如果一个类在一个类加载器中加载过,就不会从其他类加载器中装载了。
Android 提供的 DexClassloader 是按提供的 dex 顺序找的,因此对于 java 代码的热修复变得很简单 —— 只要把想要被修复的 Dex 放到最前面,加载相关的类就好了,Tinker 和 DexPatch 当然还做了更多的事情,比如对 dex 进行 merge 之类的工作。这些我们就不细细讨论了,我们今天就简单的看一看,dex 的顺序影响是不是像热修复很多文章说的那样。
构造有问题的 Dex
我们首先要构造有问题的 Dex,我们写两个类,分别为Test.java
,和HelloWorld.java
,这里的HelloWorld
类作为主入口,Test 类内容如下:
public class Test {
public void run() {
System.out.println("Bug!");
}
}
那么打印一行 Bug! 字符串,代表里面有 Bug,我们需要修复。那么HelloWorld
类自然是使用这个类了,它的代码如下:
public class HelloWorld {
public static void main(String[] args) {
Test test = new Test();
test.run();
}
}
这样,我们去调用 HelloWorld main 方法的时候,就能调用到 Test 类里面的 run 方法,默认输出一个Bug!
,我们来做一下,编译依旧很简单,可以参照我上一篇文章,这里直接给出编译命令:
javac Test.java HelloWorld.java
dx --dex -output=classes.dex Test.class HelloWorld.class
这样就产出了一个classses.dex
,然后我们把它 push 到我们的手机上,在此之前,我们
adb push classes.dex /sdcard/
我们开始验证这个 dex 是否能有我们期望的结果 —— 输出Bug!
adb shell
cd /sdcard/dalvikvm -cp classes.dex HelloWorld
看图可以看见非常顺利,我们构造了一个“有Bug”的 Dex,假设这个 Dex 目前存在于我们的 apk 里面,那我们如何使用一个新的 Dex
去覆盖我们的 Test.run 方法呢?
构造修复后的 Dex
很简单,首先,在相同的类里面,把我们的代码改了,比如这样,在 Test.java 中:
public class Test {
public void run() {
System.out.println("Fixed!");
}
}
那么,我们把这个 Test 单独制成一个Dex
文件,注意我们构造 Dex 的时候和上面不一样的地方:
~/javac Test.java HelloWorld.java
~/dx --dex --output=new.dex Test.class
注意到,我们这时候和HelloWorld.java
没有半毛钱关系,我们这时候构造出了一个新的new.dex
,我们再把它 push 到手机上
adb push new.dex /sdcard/
可以看见我的 sd 卡下面有两个 dex,一个是新的new.dex
,一个是老的classes.dex
,这时候就要见证奇迹了。
应用热修复
查阅相关文档,我们可以查到-cp
命令的用法是classpath1:classpath2
,于是我们这里就这么用
dalvikvm -cp new.dex:classes.dex HelloWorld
Fixed!
输出了Fixed!
,这里再贴一个截图:
我们可以看见 cp 参数的不同,调用同一个类的main
方法,输出不一样,这里就展示了如何利用dexPathList
“欺骗” Android 系统进行热修复的最核心流程。
好文推荐:
听说点“在看”的都是有前途的工程师喲